#include "TurnClient.h"
#ifdef _WIN32
#define INVALID_SOCKET  (SOCKET)(~0)
#include <ws2tcpip.h>
#include <windows.h>
#pragma comment(lib, "ws2_32.lib")
#else
#define INVALID_SOCKET  (int)(~0)
#include <sys/socket.h>
#include <arpa/inet.h>
#endif
#define MAX_TIME_OUT	10000
const uint32_t MSG_LOGIN = MAKEUINT('L', 'O', 'G', 'I');
const uint32_t MSG_LOGOUT = MAKEUINT('L', 'O', 'G', 'O');
const uint32_t MSG_HEARTBEAT = MAKEUINT('H', 'E', 'A', 'R');
const uint32_t MSG_TURN = MAKEUINT('T', 'U', 'R', 'N');
const uint32_t MSG_CLIENTS = MAKEUINT('C', 'L', 'I', 'S');
TurnClient::TurnClient(const char* ip, uint32_t port, uint32_t client)
{
	targetAddr = new sockaddr_in();
	selfID = client;
	RUDP::ConvertSockAddr(targetAddr, ip, port);
	rudp = new RUDPEx();
	if (!rudp->Initialize("0.0.0.0", 0))
		throw;
	rudp->RegisterRecvCallback(&TurnClient::InternalRecv, this);
	rudp->RegisterSendCallback(&TurnClient::InternalSend, this);
}

TurnClient::~TurnClient()
{
	delete rudp;
	delete targetAddr;
}

RUDP* TurnClient::GetUDP()
{
	return rudp;
}

void TurnClient::Connect(uint32_t client, uint32_t timeout)
{
	Disconnect();
	isConnecting = true;
	connectTimeout = timeout;
	targetID = client;
	uint8_t* msg = (uint8_t*)&targetLID;
	memcpy(msg, &targetID, sizeof(uint32_t));
	memcpy(msg + sizeof(uint32_t), &MSG_TURN, sizeof(uint32_t));
	beginConnectTs = rudp->GetTimestamp();
	rudp->SetProtocolFrame(targetLID);
}

void TurnClient::Disconnect()
{
	if (isConnected)
	{
		uint64_t v = (uint64_t)MSG_LOGOUT << 32 | selfID;
		rudp->SendTo((uint8_t*)&v, sizeof(uint64_t), targetAddr, false);
	}
	isConnecting = false;
	isConnected = false;
	connectTs = 0;
	
}

void TurnClient::Update(uint32_t timeout)
{
	if (isConnecting)
	{
		if (rudp->GetTimestamp() - connectTs > 100)
		{
			connectTs = rudp->GetTimestamp();
			selfLID = (uint64_t)MSG_LOGIN << 32 | selfID;
			rudp->SendTo((uint8_t*)&selfLID, sizeof(uint64_t), targetAddr, false);
		}
		if (rudp->GetTimestamp() - beginConnectTs > connectTimeout)
		{
			isConnecting = false;
			if (onConnectCompleted)
				onConnectCompleted(false);
		}
	}
	if (isConnected && rudp->GetTimestamp() - connectSuccessTs > 1000)
	{
		connectSuccessTs = rudp->GetTimestamp();
		selfLHID = (uint64_t)MSG_HEARTBEAT << 32 | selfID;
		uint32_t dt = rudp->GetTimestamp() - lastHBTs;
		int sendCount = dt > (MAX_TIME_OUT / 2) ? 2 : 1;
		for (int i = 0; i < sendCount; i++)
			rudp->SendTo((uint8_t*)&selfLHID, sizeof(uint64_t), targetAddr, false);
		if (dt > MAX_TIME_OUT)
			ExceptionDisconnect();
	}
	rudp->Update(timeout);
}

void TurnClient::SendTo(uint8_t* data, uint32_t size)
{
	if (!isConnected) return;
	rudp->SendTo(data, size, targetAddr, true);
}

void TurnClient::GetClients()
{
	if (!isConnected) return;
	getClientLHID = (uint64_t)MSG_CLIENTS << 32 | selfID;
	rudp->SendTo((uint8_t*)&getClientLHID, sizeof(uint64_t), targetAddr, false);
}

void TurnClient::InternalRecv(uint8_t* buffer, uint32_t len, sockaddr_in* remote, bool reliable, void* user)
{
	if (reliable)
	{
		if (onRecv)
			onRecv(buffer, len);
	}
	else
	{
		if (len == sizeof(uint64_t))
		{
			uint64_t msg = *(uint64_t*)buffer;
			if (msg == selfLID + 1)
			{
				isConnecting = false;
				isConnected = true;
				connectSuccessTs = rudp->GetTimestamp();
				if (onConnectCompleted)
					onConnectCompleted(true);
				lastHBTs = rudp->GetTimestamp();
			}
			else if (msg == selfLHID + 1)
			{
				lastHBTs = rudp->GetTimestamp();
			}
		}
		else if(len > sizeof(uint64_t))
		{
			uint64_t msg = *(uint64_t*)buffer;
			if (msg == getClientLHID + 1)
			{
				if (onGetClients)
				{
					std::vector<uint32_t> v;
					for (int i = 0; i < (len - sizeof(uint64_t)) / sizeof(uint32_t); i++)
						v.push_back(*(uint32_t*)(buffer + sizeof(uint64_t) + sizeof(uint32_t) * i));
					onGetClients(v);
				}
			}
		}
	}
}

void TurnClient::InternalSend(Message* message, SendState state, void* user)
{
	if (message->state == PSH && state == SS_MaxTimeout && isConnected && onDisconnected)
		ExceptionDisconnect();
	if(state == SS_Success)
		lastHBTs = rudp->GetTimestamp();
}

void TurnClient::ExceptionDisconnect()
{
	isConnected = false;
	Disconnect();
	rudp->ClearCache();
	if (onDisconnected)
		onDisconnected();
}
